/**
 * 
 */
package baduga.server;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

import baduga.core.Core_JsonDataSchema;

/**
 * @author Janek
 *
 */
public class Table_GameData {
	
	
	/**
	 * Dane sesji gry:
	 * 
	 * <pre>
	 * 0 - Suma wszystkich zakładów na stole
	 * 1 - Próg zakładu (Current Bed)
	 * 2 - Ilość graczy posiadających taki sam zakład 
	 * 3 - Ilość gotowych graczy do gry
	 * 4 - Ilośc graczy mogących szasztać kasą
	 * 5 - Nr rundy
	 * </pre>
	 * 
	 * 
	 * */
	protected int gameData[] = new int[] {
		0,
		0,
		0,
		0,
		0,
		0
	};
	


	
	/** Identyfikator Dealera  (W tablicy playersGameState)
	 * 
	 * <pre>
	 * [0] - UUID
	 * [1] - NickName
	 * [2] - Pozycja (W Tablicy activePlayerList)
	 * </pre>
	 * 
	 * */
	protected Object[] dealerID = new Object[] {"", "", 0};
	
	
	
	/**
	 * Przechowuje informacje o turze
	 * 
	 * <pre>
	 * [0]
	 * UID - Delikwenta
	 * 
	 * 
	 * [1]
	 * Movie Time (END) (Long)
	 * </pre>
	 * 
	 */
	Object turnData[] = new Object[2];
	
	
	
	/** Tablica uprawnień dla poszczególnych kodów ruchów
	 * <pre>
	 * 0 - Bet
	 * 1 - Check
	 * 2 - Raise
	 * 3 - Call
	 * 4 - Fold
	 * 5 - AllIn
	 * </pre>
	 * 
	 * */
	protected int[] playerMovePermission = new int[6];
	
	
	//============================================================================================
	
	
	/** Tablica graczy (Zawiera ich id, oraz status) 
	 * 
	 * <pre>
	 *  [0]
	 *  Statusy gracza
	 *  0 - Gra
	 *  1 - AllIn
	 *  2 - Fold (Łączymy tu gości z RageQuita i normalnych)
	 *  
	 *  [1]
	 *  Zakład gracza (Small Blind / Big Blind / Własny)
	 *  
	 *  [2]
	 *  Gotowość do gry
	 *  
	 *  [3]
	 *  0 - bez blinda
	 *  1 - BigBlind
	 *  2 - SmallBlind
	 *  [4]
	 *  Pozycja przy stole
	 *  </pre>
	 * 
	 * 
	 * */
	protected HashMap<String, Object[]> tabPlayers = new HashMap<String, Object[]>();
	
	/** Tablica obserwatorów (Zawiera ich id) */
	protected HashSet<String> tabObservers = new HashSet<String>();
	
	
	
	
	/**
	 *  Historia ruchów?
	 *  
	 *  <pre>
	 *  [String]
	 *  uuid Playera
	 *  
	 *  [Object]
	 *  [0] - Move id
	 *  [1] - Move args
	 *  </pre>
	 *  
	 *  */
	private HashMap<String, Object[]> playersMovesHistory = new HashMap<String, Object[]>();
	

	/**
	 * Przechowuje talie graczy
	 * 
	 */
	private HashMap<String, Player_Deck> playersDeck = new HashMap<String, Player_Deck>();
	
	/**
	 * Przechowuje identyfikatory kart:
	 * 
	 * <pre>
	 * [0] - Karty dostępne dla graczy
	 * [1] - Karty niedostępne dla graczy
	 * </pre>
	 * 
	 */
	protected ArrayList< ArrayList<Integer> > gameDeck = new ArrayList < ArrayList<Integer> >();
	
	
	
	
	/**
	 * Lista Aktywnych graczy:
	 * 
	 * <pre>
	 * Zawiera graczy którzy nie mają statusu fold, ani AllIn
	 * 
	 * Podczas gry struktóra jest aktualizowana
	 * 
	 * Struktóra Object[]
	 * [0]
	 * String - Kod Gracza
	 * [1]
	 * Integer - Pozycja w stole
	 * </pre>
	 * 
	 */
	protected ArrayList< Object[] > activePlayerList = new ArrayList<Object[]>();
	
	

	/**
	 * Lista wolnych miejsc
	 * 
	 */
	protected ArrayList< Integer > freePlaceList = new ArrayList< Integer >();
	
	
	
	//============================================================================================
	
	
	
	
	
	/**
	 * 
	 * Konfiguruje dane stołu podczas inicjalizacji
	 * 
	 */
	void prepareData()
	{
		//Init
		Serwer srv;
		
		
		//Def
		srv = Controler.getcSerwer();
		
		
		gameDeck.add(new ArrayList<Integer>() ); //Kupka kart używanych w grze
		gameDeck.add(new ArrayList<Integer>() ); //Kupa kart użytych
		
		
		//Zapełniane identyfikatorami kart
		for(int i = 0; i < srv.getCountOfCards(); i++)
		{
			gameDeck.get(0).add(i + 1);
		}
		
		
		
		//Ustawienie znaczników i Lista graczy
		gameData[4] = gameData[3];
		
	    //Ustawienie nr. Rundy
	    gameData[5] = 0;
		
		
		//Wrzucenie odnośników graczy do listy
		assignActivePlayers();
		
		
	}
	
	
	
	/**
	 * Funkcja zajmuje się sortowaniem kart
	 * 
	 * 
	 */
	private void sortCard()
	{
		//Init
		Serwer srv;
		Random randKey;
		int tmp;
		
		//Def
		srv = Controler.getcSerwer();
		randKey = new Random();
		
		//Rozkład normalny na początek
		//Zapełniane identyfikatorami kart
		for(int i = 0; i < srv.getCountOfCards(); i++)
		{
			Collections.swap(gameDeck.get(0), i, (int) (   ( Math.abs(randKey.nextGaussian()) ) *  (srv.getCountOfCards() - 1) ) );
		}
		
		
		
		//Przełożenie Talii w losowym punkt
		tmp = randKey.nextInt(srv.getCountOfCards() - 4) + 1;
		
		
		for(int i = 0; i < srv.getCountOfCards(); i++)
		{
			if(tmp < i)
			{
				Collections.swap(gameDeck.get(0), i, i - tmp );
			}
		}
		
		
		
		//Przekładanie na co 2 karty
		
		
		
		
		tmp = srv.getCountOfCards() / 2;
		
		

		//Przełożenie kilku kart
		for(int i = 1; i < tmp; i++)
		{
			Collections.swap(gameDeck.get(0), i, i + i );
			
			Collections.swap(gameDeck.get(0), tmp + i, i);
		}
		
		
		
	}
	
	
	
	
	/**
	 * 
	 * Uzupełnia deck
	 * 
	 */
	private void refuelDeck()
	{
		//Przekopiowanie
		gameDeck.set(0, (ArrayList<Integer>) gameDeck.get(1).clone());
		
		//Wyczyszczenie Tali odłożonych kart
		gameDeck.get(1).clear();
		
		sortCard();
	}
	
	
	/**
	 * Funkcja zwalnia zużyte karty
	 * 
	 * @param inputData pozycje kart {Podane przez graczy}
	 * @param objP
	 */
	private void recycleUsedCard(Object[] inputData, Player objP)
	{
		//def.
		int tmp;
		Player_Deck objDeck;
		ArrayList<Integer> tmpGameDeck;
		
		//Init
		objDeck = playersDeck.get( objP.getPlayerId() );
		tmpGameDeck = gameDeck.get(1);
		
		
		for(int i = 0; i < inputData.length; i++)
		{
			tmp = objDeck.getDeckElement( (Integer) inputData[i] );
			
			//Dodaje zużyte karty
			tmpGameDeck.add(tmp); 
		}
		
		
	}
	
	
	
	/**
	 * 
	 * Aktualizuje decka gracza
	 * 
	 * @param inputData Zawiera pozycje kart w talli, Jeżeli jest null oznacza wgranie całej talli
	 * @param cardsID
	 * @param playerID
	 */
	protected void assignNewCards(Object[] inputData, Integer[] cardsID, String playerID)
	{
		
		//def.
		Player_Deck objDeck;

		
		//Init
		objDeck = playersDeck.get( playerID );

		if(inputData != null)
		{
			
			for(int i = 0; i < inputData.length; i++)
			{
				objDeck.updateDeck((Integer) inputData[i], cardsID[i]);
			}
			
		}
		else
		{
			objDeck.setDeck(cardsID.clone());
		}
		
	}

	
	
	/**
	 * funkcja pobiera z stosu n kart
	 * 
	 * @param amount 
	 * @return int[0 - 4] {Zwraca Tablicę z ID kart}
	 */
	protected Integer[] getNrandomCard(final int amount)
	{
		//init
		Integer[] result;
		int sizeOfGameDeck, 
			tmp, 
			k;
		
		//Def. 
		result = new Integer[amount];
		k = 0;
		
		
		//Spr. Czy stos pomieści wszystkie karty
		sizeOfGameDeck = gameDeck.get(0).size();
		
		
		if(sizeOfGameDeck >= amount)
		{
			//Skorygowanie pozycji startowej do indeksu [0, n-1]
			--sizeOfGameDeck;
			
			tmp = sizeOfGameDeck - amount; //Ustawienie Limitu

			
			for(int i = sizeOfGameDeck - 1; i > tmp; i--)
			{
				result[k] = gameDeck.get(0).remove(i);
				
				++k; //zwiększenie licznika
			}

		}
		else // Z przełożeniem
		{
			
			
			//Skorygowanie pozycji startowej do indeksu [0, n-1]
			--sizeOfGameDeck;
			
			tmp =  amount - sizeOfGameDeck; //Obliczenie ile brakuje

			
			for(int i = sizeOfGameDeck - 1; i > -1; i--)
			{
				result[k] = gameDeck.get(0).remove(i);
				
				++k; //zwiększenie licznika
			}
			
			//Przeładowanie talli
			this.refuelDeck();
			
			
			
			//Pobranie nowego rozmiaru
			sizeOfGameDeck = gameDeck.get(0).size();
			
			//Wyliczenie limitu
			tmp = sizeOfGameDeck - tmp;
			
			
			
			for(int i = sizeOfGameDeck - 1; i > tmp; i--)
			{
				result[k] = gameDeck.get(0).remove(i);
				
				++k; //zwiększenie licznika
			}

			
		}
		
		
		return result;
		
	}
	
	
	
	
	//public void send

	
	
	
	
	//============================================================================================
	
	
	/**
	 * 
	 * Uzupełnia liste graczy
	 * 
	 */
	protected void assignActivePlayers()
	{
		//
		
		
		
		
		//Def.
		Object pData[];
		int k;
		
		//Init.
		/** Okresla pozycje przy stole */
		k = 0;
		
		synchronized(this)
		{
			for(String playerID : tabPlayers.keySet())
			{
				
				pData = tabPlayers.get(playerID);
				
				if(pData != null && (Integer) pData[0] == 0)
				{
					activePlayerList.add(new Object[] {playerID, k});
				}
				
				++k;
				
			}
		}
	}
	
	
	
	/**
	 *  Generuje tablicą dozwolonych ruchów dla gracza
	 *  
	 *  result:
	 *  0 - nie możliwe
	 *  1 - możliwe
	 *  
	 *
	 * @param objP
	 * @return
	 */
	protected void movePermision(final Player objP)
	{
		//Def.
		int[] result;
		
		int tmpNum;
		Object[] tmp;
		
		//Init. 
		result = new int[6];
		tmp = tabPlayers.get(objP.getPlayerId());
		
		
		
		//Szczególne zapisy dot. 1-szej rundy
		if(gameData[5] != 1)
		{
			/** Bet */
			
			//Jeżeli nikt nie postawił wcześniej zakładu, oraz gracz ma na to środki
			if(gameData[1] == 0 && objP.getFunds() > 0)
			{
				result[0] = 1;
			}
			else
			{
				result[0] = 0;
			}
			
			
			
			
			/** Check */
			
			//Jeżeli nikt nie podbił stawki
			if(gameData[1] == 0)
			{
				result[1] = 1;
			}
			else
			{
				result[1] = 0;
			}
			
			/** Raise */
			
			//Jeżeli mój zakład jest niższy od stołu i jestem w stanie podbić o wartośc różnicy + 1 lub mam taki sam zakład oraz kwotę na koncie większa od 0
			
			//Sprawdzamy, czy gracz może przebić
			tmpNum = gameData[1] + 1 - (Integer) tmp[1];
			if(gameData[1] > (Integer) tmp[1] && objP.checkFunds(tmpNum) || gameData[2] > 1  && objP.getFunds() > 0)
			{
				result[2] = 1;
			}
			else
			{
				result[2] = 0;
			}
			

			
		}
		else //1-sza runda
		{

			/** Bet */
			result[0] = 0;
			
			/** Check */
			
			
			//Jeżeli gracz jest z dużą ciemną i nie przebito stawką dużej ciemnej
			if((Integer) tmp[3] == 1 && gameData[1] <= (Integer) tmp[1])
			{
				result[1] = 1;
			}
			else
			{
				result[1] = 0;
			}
			
			
			/** Raise */
			//Jeżeli jestem graczem z big blind i nie przebito mojej stawki lub moja stawka jest równa stawce stołu, ale ten sam zakład ma kilka graczy oraz mam środki na podbicie stawki (> 0) 
			//Jeżeli mam mniejszy zakład, ale jestem w stanie podbić o różnicę + 1
			
			//Sprawdzamy, czy gracz może przebić
			tmpNum = gameData[1] + 1 - (Integer) tmp[1];
			
			if( ((Integer) tmp[3] == 1 && result[1] == 1 || gameData[2] > 1 && objP.getFunds() > 0 ) && objP.getFunds() > 0 || gameData[1] > (Integer) tmp[1] && objP.checkFunds(tmpNum)   )
			{
				result[2] = 1;
			}
			else
			{
				result[2] = 0;
			}
			
		}
		
		
		/** Call */
		
		
		//różnica między mną, a zakładem stołu
		tmpNum = gameData[1] - (Integer) tmp[1];
		
		//Wyrównanie do aktualnego zakładu (Jeśli różnica > 0 & stać mnie)
		if(tmpNum > 0 && objP.checkFunds(tmpNum))
		{
			result[3] = 1;
		}
		else
		{
			result[3] = 0;
		}
		
		
		
		/** AllIn */
		//Jeżeli nie mogę wykonać raise i call
		if(result[3] == 0 && result[2] == 0)
		{
			result[5] = 1;
		}
		else
		{
			result[5] = 0;
		}
		
		
		
		/** Fold */
		
		//Oddanie za darmo środków? Dlaczego nie?
		result[4] = 1;
		
		
		//Przekopiowanie
		playerMovePermission =  result.clone();
	}
	
	
	

	
	
	
	
	
	
	
	/**
	 * Zwraca absolutą pozycje gracza przy stole
	 * 
	 * @param pID
	 * @return
	 */
	protected int getPlayerAbsolutePositionInTable(final String pID)
	{
		
		
		//Def.
		int poz;
		Object[] pData;
		
		//Init.
		poz = 0;
		
		pData = this.tabPlayers.get(pID);
		
		
		
		if(pData != null)
		{
			poz = (Integer) pData[4];
		}
		
		
		return poz;
	}
	
	
	
	
	
	
	/**
	 * Szuka wolnego identyfikatora przy Stole
	 * 
	 * @return
	 */
	protected int getFreePlaceInTable()
	{
		return this.freePlaceList.remove(0);
	}

	
	
	/**
	 * Pobiera pozycje w stole gracza
	 * 
	 */
	protected int getPlayerTablePosition(final String pId)
	{
		//
		
		
		
		//Def.
		int k;
		
		//Init.
		/** Okresla pozycje przy stole */
		k = 0;
		
		for(String playerID : tabPlayers.keySet())
		{
			
			if(!pId.equals(playerID))
			{
			
				++k;
			}
			else
			{
				break;
			}
			
		}
		
		
		return k;
	}
	
	
	
	//============================================================================================
	
	
	/**
	 * Sprawdza czy nastąpił już moment końca gry
	 * 
	 * @return
	 */
	public boolean endOfBid()
	{
		boolean result;
		
		if(gameData[2] == gameData[4])
		{
			result = true;
		}
		else
		{
			result = false;
		}
		
		
		
		return result;
	}
	
	
	/**
	 * Sprawdza warunek końca gry
	 * 
	 */
	
	public boolean endGameTest()
	{
		if(activePlayerList.size() == 1)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	
	
	//============================================================================================
	
	/**
	 * Zwraca czas unixowy w sekundach
	 * 
	 * @return
	 */
	protected long getUnixtime()
	{
		return System.currentTimeMillis() / 1000L;
	}

	
	
	
	
	
	
	
	
	
	
	//================================================================
	
	
	/**
	 * Schemat do komunikacji w JSonie
	 * 
	 * 
	 * @param code Kod segmentu
	 * @param args Argumenty
	 * @param dataD1
	 * @param dataD2
	 * @return
	 */
	public Core_JsonDataSchema jsonSchema(final int code, final int[] args, final Object[] dataD1, final Object[][] dataD2)
	{
		Core_JsonDataSchema jsonDataSchema = new Core_JsonDataSchema();
		
		jsonDataSchema .setKod(code);
		
		
		
	  	if(dataD1 != null)
	  	{
	  		jsonDataSchema .setDataD1(
	  				dataD1
	  		);
	  	}
		
		
		
	  	if(dataD2 != null)
	  	{
	  		jsonDataSchema .setDataD2(
	  				dataD2
	  		);
	  	}
	  	
	  	return jsonDataSchema;
	}
	
	
	
	
	
	
	
	
	//=================================================================
	
	/**
	 * 
	 * Wysyła wiadomość do wszystkich graczy:
	 * 
	 * <pre>
	 * [cfgData]
	 * [0] - Tryb pracy
	 * 	[0] - Wysyła do wszystkich po za okreslonym graczem
	 * 	[1] - Wysyła do wszystkich
	 * 
	 * [1] - UUID Gracza, potrzebny gdy pkt [0] jest jako 1-ka
	 * </pre>
	 * 
	 * 
	 * @param cfgData
	 * @param inputData
	 */
	void broadcastMessageToAll(Object[] cfgData, Core_JsonDataSchema inputData )
	{
		//Def.
		Object[] tmpD;
		Player tmpPl;
		final Serwer srv;
		Network_API__Client_Thread playerTh;
		
		//Init. 
		srv = Controler.getcSerwer();
		
		
		/** Wysyła do wszystkich oprócz gracza */
		if((Integer) cfgData[0] == 1)
		{
			for(final String objID : this.tabPlayers.keySet())
			{
				//Pobiera dane po za graczem
				if(objID != null && !objID.equals((String) cfgData[1]))
				{
					
					tmpD = srv.getPlayer(objID);
					
					if((boolean) tmpD[0] == true)
					{
						//Init
						tmpPl = (Player) tmpD[1];
						playerTh = tmpPl.getMyThread();
						
						
						//Konstruowanie wiadomości
						playerTh.sendData( inputData );
	
					}
				}
			}
		}
		else
		{
			for(final String objID : this.tabPlayers.keySet())
			{

					
				tmpD = srv.getPlayer(objID);
					
				if((boolean) tmpD[0] == true)
				{
					//Init
					tmpPl = (Player) tmpD[1];
					playerTh = tmpPl.getMyThread();
						
						
					//Konstruowanie wiadomości
					playerTh.sendData( inputData );
	
				}

			}
		}
		
		
		
		/** Jeżeli wielkośc jest większa od 0*/
		if(this.tabObservers.size() > 0)
		{
			Set<String> tmpSet = this.tabObservers;
			
			/** Obserwatorzy */
			for(final String objID : tmpSet)
			{
	
				tmpD = srv.getPlayer(objID);
					
				if((boolean) tmpD[0] == true)
				{
					//Init
					tmpPl = (Player) tmpD[1];
					playerTh = tmpPl.getMyThread();
						
						
					//Konstruowanie wiadomości
					playerTh.sendData( inputData );
	
				}
	
			}
		}
	}
	
	//============================================================================================
	
	
	
	
	
	
	
	
	
	
	
	/**
	 * Zwraca póle ze stołu
	 * 
	 * @return
	 */
	public int getPot()
	{
		return gameData[1];
	}
	
	
	/**
	 * Pobiera NickName Dealera
	 * 
	 * @return
	 */
	public String getDealerName()
	{
		return (String) dealerID[1];
	}
	
	
	
	
	/**
	 * Pobiera Historią ruchów z stołu
	 * 
	 * @param uid
	 * @return
	 */
	public Object[] getMoveHistory(String uid)
	{
		return playersMovesHistory.get(uid);
	}
	
	
	/**
	 * Zwraca tablicę dozwolonych ruchów
	 * 
	 * @return
	 */
	public int[] getPlayerMovePermission()
	{
		return playerMovePermission;
	}
	
	
	/**
	 * Zwraca nrAktualnej rundy
	 * @return
	 */
	public int getRunda()
	{
		return this.gameData[ 5 ];
	}
	
	
	//====================================================================
	
	
	/**
	 * Aktualizacuje póle stołu
	 * 
	 * @return
	 */
	public void updatePot(final int inpCash)
	{
		gameData[1] += inpCash;
	}
	
	
	/**
	 * Aktualizuje liczbę gotowych graczy
	 * 
	 * +1 - Gracz dołączył 
	 * -1 - Gracz odszedł
	 * 
	 * @return
	 */
	public void incReadyPlayerCount(int amount)
	{
		gameData[3] += amount;
	}
	
	
	/**
	 * Ustawia nrAktualnej rundy
	 * @param nrRundy
	 */
	public void setRunda(final int nrRundy)
	{
		this.gameData[ 5 ] = nrRundy;
	}
	
	


}
